<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
 <head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <title>协变与逆变</title>
<link media="all" rel="stylesheet" type="text/css" href="styles/03e73060321a0a848018724a6c83de7f-theme-base.css" />
<link media="all" rel="stylesheet" type="text/css" href="styles/03e73060321a0a848018724a6c83de7f-theme-medium.css" />

 </head>
 <body class="docs"><div class="navbar navbar-fixed-top">
  <div class="navbar-inner clearfix">
    <ul class="nav" style="width: 100%">
      <li style="float: left;"><a href="language.oop5.serialization.html">« 对象序列化</a></li>
      <li style="float: right;"><a href="language.oop5.changelog.html">OOP 变更日志 »</a></li>
    </ul>
  </div>
</div>
<div id="breadcrumbs" class="clearfix">
  <ul class="breadcrumbs-container">
    <li><a href="index.html">PHP Manual</a></li>
    <li><a href="language.oop5.html">类与对象</a></li>
    <li>协变与逆变</li>
  </ul>
</div>
<div id="layout">
  <div id="layout-content"><div id="language.oop5.variance" class="sect1">
 <h2 class="title">协变与逆变</h2>

 <p class="para">
  在 PHP 7.2.0 里，通过对子类方法里参数的类型放宽限制，实现对逆变的部分支持。
  自 PHP 7.4.0 起开始支持完整的协变和逆变。
 </p>
 <p class="para">
  协变使子类比父类方法能返回更具体的类型；
  逆变使子类比父类方法参数类型能接受更模糊的类型。
 </p>
 <p class="para">
  在以下情况下，类型声明被认为更具体：
  <ul class="itemizedlist">
   <li class="listitem">
    <span class="simpara">
     在 <a href="language.types.type-system.html#language.types.type-system.composite.union" class="link">联合类型</a> 中删除类型
    </span>
   </li>
   <li class="listitem">
    <span class="simpara">
     在
     <a href="language.types.type-system.html#language.types.type-system.composite.intersection" class="link">交集类型</a> 中添加类型
    </span>
   </li>
   <li class="listitem">
    <span class="simpara">
     类类型（class type）修改为子类类型
    </span>
   </li>
   <li class="listitem">
    <span class="simpara">
     <span class="type"><a href="language.types.iterable.html" class="type iterable">iterable</a></span> 修改为 <span class="type">array</span> 或者 <span class="classname"><a href="class.traversable.html" class="classname">Traversable</a></span>
    </span>
   </li>
  </ul>

  如果情况相反，则类型类被认为是模糊的。
 </p>

 <div class="sect2" id="language.oop5.variance.covariance">
  <h3 class="title">协变</h3>

  <p class="para">
   创建一个名为 <var class="varname">Animal</var> 的简单的抽象父类，用于演示什么是协变。
   两个子类：<var class="varname">Cat</var> 和 <var class="varname">Dog</var> 扩展（extended）了 <var class="varname">Animal</var>。
  </p>

  <div class="informalexample">
   <div class="example-contents">
<div class="phpcode"><code><span style="color: #000000">
<span style="color: #0000BB">&lt;?php<br /><br /></span><span style="color: #007700">abstract&nbsp;class&nbsp;</span><span style="color: #0000BB">Animal<br /></span><span style="color: #007700">{<br />&nbsp;&nbsp;&nbsp;&nbsp;protected&nbsp;</span><span style="color: #0000BB">string&nbsp;$name</span><span style="color: #007700">;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;function&nbsp;</span><span style="color: #0000BB">__construct</span><span style="color: #007700">(</span><span style="color: #0000BB">string&nbsp;$name</span><span style="color: #007700">)<br />&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000BB">$this</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">name&nbsp;</span><span style="color: #007700">=&nbsp;</span><span style="color: #0000BB">$name</span><span style="color: #007700">;<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;abstract&nbsp;public&nbsp;function&nbsp;</span><span style="color: #0000BB">speak</span><span style="color: #007700">();<br />}<br /><br />class&nbsp;</span><span style="color: #0000BB">Dog&nbsp;</span><span style="color: #007700">extends&nbsp;</span><span style="color: #0000BB">Animal<br /></span><span style="color: #007700">{<br />&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;function&nbsp;</span><span style="color: #0000BB">speak</span><span style="color: #007700">()<br />&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;echo&nbsp;</span><span style="color: #0000BB">$this</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">name&nbsp;</span><span style="color: #007700">.&nbsp;</span><span style="color: #DD0000">"&nbsp;barks"</span><span style="color: #007700">;<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br />}<br /><br />class&nbsp;</span><span style="color: #0000BB">Cat&nbsp;</span><span style="color: #007700">extends&nbsp;</span><span style="color: #0000BB">Animal&nbsp;<br /></span><span style="color: #007700">{<br />&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;function&nbsp;</span><span style="color: #0000BB">speak</span><span style="color: #007700">()<br />&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;echo&nbsp;</span><span style="color: #0000BB">$this</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">name&nbsp;</span><span style="color: #007700">.&nbsp;</span><span style="color: #DD0000">"&nbsp;meows"</span><span style="color: #007700">;<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br />}</span>
</span>
</code></div>
   </div>

  </div>

  <p class="para">
   注意：在这个例子中，没有方法返回了值。
   将通过添加个别工厂方法，创建并返回 <var class="varname">Animal</var>、<var class="varname">Cat</var>、<var class="varname">Dog</var> 
   类型的新对象。
  </p>

  <div class="informalexample">
   <div class="example-contents">
<div class="phpcode"><code><span style="color: #000000">
<span style="color: #0000BB">&lt;?php<br /><br /></span><span style="color: #007700">interface&nbsp;</span><span style="color: #0000BB">AnimalShelter<br /></span><span style="color: #007700">{<br />&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;function&nbsp;</span><span style="color: #0000BB">adopt</span><span style="color: #007700">(</span><span style="color: #0000BB">string&nbsp;$name</span><span style="color: #007700">):&nbsp;</span><span style="color: #0000BB">Animal</span><span style="color: #007700">;<br />}<br /><br />class&nbsp;</span><span style="color: #0000BB">CatShelter&nbsp;</span><span style="color: #007700">implements&nbsp;</span><span style="color: #0000BB">AnimalShelter<br /></span><span style="color: #007700">{<br />&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;function&nbsp;</span><span style="color: #0000BB">adopt</span><span style="color: #007700">(</span><span style="color: #0000BB">string&nbsp;$name</span><span style="color: #007700">):&nbsp;</span><span style="color: #0000BB">Cat&nbsp;</span><span style="color: #FF8000">//&nbsp;返回类的类型不仅限于&nbsp;Animal，还可以是&nbsp;Cat&nbsp;类型<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #007700">{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;new&nbsp;</span><span style="color: #0000BB">Cat</span><span style="color: #007700">(</span><span style="color: #0000BB">$name</span><span style="color: #007700">);<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br />}<br /><br />class&nbsp;</span><span style="color: #0000BB">DogShelter&nbsp;</span><span style="color: #007700">implements&nbsp;</span><span style="color: #0000BB">AnimalShelter<br /></span><span style="color: #007700">{<br />&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;function&nbsp;</span><span style="color: #0000BB">adopt</span><span style="color: #007700">(</span><span style="color: #0000BB">string&nbsp;$name</span><span style="color: #007700">):&nbsp;</span><span style="color: #0000BB">Dog&nbsp;</span><span style="color: #FF8000">//&nbsp;返回类的类型不仅限于&nbsp;Animal，还可以是&nbsp;Dog&nbsp;类型<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #007700">{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;new&nbsp;</span><span style="color: #0000BB">Dog</span><span style="color: #007700">(</span><span style="color: #0000BB">$name</span><span style="color: #007700">);<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br />}<br /><br /></span><span style="color: #0000BB">$kitty&nbsp;</span><span style="color: #007700">=&nbsp;(new&nbsp;</span><span style="color: #0000BB">CatShelter</span><span style="color: #007700">)-&gt;</span><span style="color: #0000BB">adopt</span><span style="color: #007700">(</span><span style="color: #DD0000">"Ricky"</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">$kitty</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">speak</span><span style="color: #007700">();<br />echo&nbsp;</span><span style="color: #DD0000">"\n"</span><span style="color: #007700">;<br /><br /></span><span style="color: #0000BB">$doggy&nbsp;</span><span style="color: #007700">=&nbsp;(new&nbsp;</span><span style="color: #0000BB">DogShelter</span><span style="color: #007700">)-&gt;</span><span style="color: #0000BB">adopt</span><span style="color: #007700">(</span><span style="color: #DD0000">"Mavrick"</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">$doggy</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">speak</span><span style="color: #007700">();</span>
</span>
</code></div>
   </div>

   <p class="para">以上例程会输出：</p>
   <div class="example-contents screen">
<div class="cdata"><pre>
Ricky meows
Mavrick barks
</pre></div>
   </div>
  </div>
 </div>

 <div class="sect2" id="language.oop5.variance.contravariance">
  <h3 class="title">逆变</h3>

  <p class="para">
   继续上一个例子，除了 <var class="varname">Animal</var>、
   <var class="varname">Cat</var>、<var class="varname">Dog</var>，我们还添加了 <var class="varname">Food</var>、<var class="varname">AnimalFood</var> 类，
   同时为抽象类 <var class="varname">Animal</var> 添加了一个 <var class="varname">eat(AnimalFood $food)</var> 方法。
  </p>

  <div class="informalexample">
   <div class="example-contents">
<div class="phpcode"><code><span style="color: #000000">
<span style="color: #0000BB">&lt;?php<br /><br /></span><span style="color: #007700">class&nbsp;</span><span style="color: #0000BB">Food&nbsp;</span><span style="color: #007700">{}<br /><br />class&nbsp;</span><span style="color: #0000BB">AnimalFood&nbsp;</span><span style="color: #007700">extends&nbsp;</span><span style="color: #0000BB">Food&nbsp;</span><span style="color: #007700">{}<br /><br />abstract&nbsp;class&nbsp;</span><span style="color: #0000BB">Animal<br /></span><span style="color: #007700">{<br />&nbsp;&nbsp;&nbsp;&nbsp;protected&nbsp;</span><span style="color: #0000BB">string&nbsp;$name</span><span style="color: #007700">;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;function&nbsp;</span><span style="color: #0000BB">__construct</span><span style="color: #007700">(</span><span style="color: #0000BB">string&nbsp;$name</span><span style="color: #007700">)<br />&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000BB">$this</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">name&nbsp;</span><span style="color: #007700">=&nbsp;</span><span style="color: #0000BB">$name</span><span style="color: #007700">;<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;function&nbsp;</span><span style="color: #0000BB">eat</span><span style="color: #007700">(</span><span style="color: #0000BB">AnimalFood&nbsp;$food</span><span style="color: #007700">)<br />&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;echo&nbsp;</span><span style="color: #0000BB">$this</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">name&nbsp;</span><span style="color: #007700">.&nbsp;</span><span style="color: #DD0000">"&nbsp;eats&nbsp;"&nbsp;</span><span style="color: #007700">.&nbsp;</span><span style="color: #0000BB">get_class</span><span style="color: #007700">(</span><span style="color: #0000BB">$food</span><span style="color: #007700">);<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br />}</span>
</span>
</code></div>
   </div>

  </div>

  <p class="para">
   为了演示什么是逆变，<var class="varname">Dog</var> 类重写（overridden）了 <var class="varname">eat</var> 方法，
   允许传入任意 <var class="varname">Food</var> 类型的对象。
   而 <var class="varname">Cat</var> 类保持不变。
  </p>

  <div class="informalexample">
   <div class="example-contents">
<div class="phpcode"><code><span style="color: #000000">
<span style="color: #0000BB">&lt;?php<br /><br /></span><span style="color: #007700">class&nbsp;</span><span style="color: #0000BB">Dog&nbsp;</span><span style="color: #007700">extends&nbsp;</span><span style="color: #0000BB">Animal<br /></span><span style="color: #007700">{<br />&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;function&nbsp;</span><span style="color: #0000BB">eat</span><span style="color: #007700">(</span><span style="color: #0000BB">Food&nbsp;$food</span><span style="color: #007700">)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;echo&nbsp;</span><span style="color: #0000BB">$this</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">name&nbsp;</span><span style="color: #007700">.&nbsp;</span><span style="color: #DD0000">"&nbsp;eats&nbsp;"&nbsp;</span><span style="color: #007700">.&nbsp;</span><span style="color: #0000BB">get_class</span><span style="color: #007700">(</span><span style="color: #0000BB">$food</span><span style="color: #007700">);<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br />}</span>
</span>
</code></div>
   </div>

  </div>

  <p class="para">
  下面的例子展示了逆变。
  </p>

  <div class="informalexample">
   <div class="example-contents">
<div class="phpcode"><code><span style="color: #000000">
<span style="color: #0000BB">&lt;?php<br /><br />$kitty&nbsp;</span><span style="color: #007700">=&nbsp;(new&nbsp;</span><span style="color: #0000BB">CatShelter</span><span style="color: #007700">)-&gt;</span><span style="color: #0000BB">adopt</span><span style="color: #007700">(</span><span style="color: #DD0000">"Ricky"</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">$catFood&nbsp;</span><span style="color: #007700">=&nbsp;new&nbsp;</span><span style="color: #0000BB">AnimalFood</span><span style="color: #007700">();<br /></span><span style="color: #0000BB">$kitty</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">eat</span><span style="color: #007700">(</span><span style="color: #0000BB">$catFood</span><span style="color: #007700">);<br />echo&nbsp;</span><span style="color: #DD0000">"\n"</span><span style="color: #007700">;<br /><br /></span><span style="color: #0000BB">$doggy&nbsp;</span><span style="color: #007700">=&nbsp;(new&nbsp;</span><span style="color: #0000BB">DogShelter</span><span style="color: #007700">)-&gt;</span><span style="color: #0000BB">adopt</span><span style="color: #007700">(</span><span style="color: #DD0000">"Mavrick"</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">$banana&nbsp;</span><span style="color: #007700">=&nbsp;new&nbsp;</span><span style="color: #0000BB">Food</span><span style="color: #007700">();<br /></span><span style="color: #0000BB">$doggy</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">eat</span><span style="color: #007700">(</span><span style="color: #0000BB">$banana</span><span style="color: #007700">);</span>
</span>
</code></div>
   </div>

   <p class="para">以上例程会输出：</p>
   <div class="example-contents screen">
<div class="cdata"><pre>
Ricky eats AnimalFood
Mavrick eats Food
</pre></div>
   </div>

   <p class="para">
   但 <var class="varname">$kitty</var> 若尝试 
   <var class="varname">eat</var> <var class="varname">$banana</var>
   会发生什么呢？
   </p>

   <div class="example-contents">
<div class="phpcode"><code><span style="color: #000000">
$kitty-&gt;eat($banana);</span>
</code></div>
   </div>

   <p class="para">以上例程会输出：</p>
   <div class="example-contents screen">
<div class="cdata"><pre>
Fatal error: Uncaught TypeError: Argument 1 passed to Animal::eat() must be an instance of AnimalFood, instance of Food given
</pre></div>
   </div>
  </div>
 </div>
</div></div></div></body></html>